removed uncomple web ui and put it on a different branch to avoid confusion, rmoved common folder with files for redis and grpc as they are currently not used, adjusted dev container

This commit is contained in:
Jean Jacques Avril 2025-01-05 09:15:50 +00:00
parent 178bd81d9c
commit c83a797075
No known key found for this signature in database
58 changed files with 11 additions and 12964 deletions

View File

@ -1,16 +1,16 @@
#!/bin/bash #!/bin/bash
# Frontend setup ## Frontend setup
if [ -d "frontend-react" ]; then #if [ -d "frontend-react" ]; then
echo "Setting up frontend-react..." # echo "Setting up frontend-react..."
( # (
cd frontend-react # cd frontend-react
# npm install # # npm install
deno install --allow-scripts # deno install --allow-scripts
) & # ) &
else #else
echo "frontend-react directory not found, skipping..." # echo "frontend-react directory not found, skipping..."
fi #fi
# Dart backend setup # Dart backend setup
if [ -d "backend-dart" ]; then if [ -d "backend-dart" ]; then

View File

@ -1,7 +0,0 @@
# Redis Konfigurationsdatei für Persistenz
save 900 1
save 300 10
save 60 10000
# AOF aktivieren für zusätzliche Datensicherheit
appendonly yes

View File

@ -1,94 +0,0 @@
syntax = "proto3";
package timetracking;
service TimeTrackingService {
rpc RegisterUser(RegisterUserRequest) returns (RegisterUserResponse);
rpc LoginUser(LoginUserRequest) returns (LoginUserResponse);
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc CreateProject(CreateProjectRequest) returns (CreateProjectResponse);
rpc StartWorkSession(StartWorkSessionRequest) returns (StartWorkSessionResponse);
rpc StopWorkSession(StopWorkSessionRequest) returns (StopWorkSessionResponse);
rpc GenerateReport(GenerateReportRequest) returns (GenerateReportResponse);
}
message RegisterUserRequest {
string name = 1;
string email = 2;
string password = 3;
}
message RegisterUserResponse {
string userId = 1;
string message = 2;
}
message LoginUserRequest {
string email = 1;
string password = 2;
}
message LoginUserResponse {
string token = 1;
}
message GetUserRequest {
string userId = 1;
}
message GetUserResponse {
string userId = 1;
string name = 2;
string email = 3;
}
message CreateProjectRequest {
string name = 1;
string clientId = 2;
string description = 3;
}
message CreateProjectResponse {
string projectId = 1;
string message = 2;
}
message StartWorkSessionRequest {
string userId = 1;
string projectId = 2;
string description = 3;
}
message StartWorkSessionResponse {
string sessionId = 1;
}
message StopWorkSessionRequest {
string sessionId = 1;
}
message StopWorkSessionResponse {
string message = 1;
}
message GenerateReportRequest {
repeated string userIds = 1;
string startDate = 2;
string endDate = 3;
string projectId = 4;
}
message GenerateReportResponse {
string reportId = 1;
string generatedAt = 2;
repeated WorkSession sessions = 3;
}
message WorkSession {
string sessionId = 1;
string userId = 2;
string startTime = 3;
string endTime = 4;
string projectId = 5;
string description = 6;
}

View File

@ -1,3 +0,0 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
}

View File

@ -1,40 +0,0 @@
# 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*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View File

@ -1,36 +0,0 @@
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.

View File

@ -1,3 +0,0 @@
{
"unstable": ["unsafe-proto"]
}

3287
frontend-react/deno.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +0,0 @@
{
"imports": {
"@/components/": "./components/"
}
}

View File

@ -1,30 +0,0 @@
{
"Error": {
"description": "<p>Es ist leider ein Problem aufgetreten.</p><p>Du kannst versuchen <retry>diese Seite neu zu laden</retry>.</p>",
"title": "Etwas ist schief gelaufen!",
"backToHome": "Zurück zur Startseite",
"back": "Zurück",
"defaultErrorCode": "Fehler",
"defaultErrorMessage": "Es ist ein Problem aufgetreten."
},
"LocaleSwitcher": {
"label": "Sprache ändern",
"locale": "{locale, select, de {🇩🇪 Deutsch} en {🇺🇸 English} other {Unbekannt}}"
},
"Navigation": {
"home": "Start",
"pathnames": "Pfadnamen"
},
"NotFoundPage": {
"description": "Bitte überprüfe die Addressleiste deines Browsers oder verwende die Navigation um zu einer bekannten Seite zu wechseln.",
"title": "Seite nicht gefunden"
},
"Auth": {
"login": "Anmelden",
"logout": "Abmelden",
"profile": "Profil",
"register": "Registrieren",
"resetPassword": "Passwort zurücksetzen"
}
}

View File

@ -1,27 +0,0 @@
{
"HomePage": {
"title": "Hello world!",
"about": "Go to the about page"
},
"Error": {
"description": "<p>We've unfortunately encountered an error.</p><p>You can try to <retry>reload the page</retry> you were visiting.</p>",
"title": "Something went wrong!",
"backToHome": "Back to home",
"back": "Back",
"defaultErrorCode": "Error",
"defaultErrorMessage": "An error occurred."
},
"LocaleSwitcher": {
"label": "Change language",
"locale": "{locale, select, de {🇩🇪 Deutsch} en {🇺🇸 English} other {Unknown}}"
},
"Navigation": {
"home": "Home",
"pathnames": "Pathnames"
},
"NotFoundPage": {
"description": "Please check the address bar of your browser or use the navigation to go to a known page.",
"title": "Page not found"
}
}

View File

@ -1,23 +0,0 @@
import type { NextConfig } from "next";
import createNextIntlPlugin from "next-intl/plugin";
const withNextIntl = createNextIntlPlugin();
const nextConfig: NextConfig = {
/* config options here */
sassOptions: {
silenceDeprecations: ["legacy-js-api"],
},
async rewrites() {
return [
{
source: "/api/:path*",
destination: `${process.env.API_SERVER}/:path*`, // Zielserver aus Umgebungsvariable
},
];
},
// This is required to support PostHog trailing slash API requests
skipTrailingSlashRedirect: true,
};
export default withNextIntl(nextConfig);

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +0,0 @@
{
"name": "frontend-react",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@reactivex/rxjs": "^6.6.7",
"clsx": "^2.1.1",
"fp-ts": "^2.16.9",
"lucide-react": "^0.456.0",
"next": "15.0.3",
"next-intl": "^3.25.0",
"react": "19.0.0-rc-66855b96-20241106",
"react-dom": "19.0.0-rc-66855b96-20241106",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.4.20",
"eslint": "^8",
"eslint-config-next": "15.0.3",
"i18next-parser": "^9.0.2",
"postcss": "^8.4.47",
"sass": "^1.80.6",
"tailwindcss": "^3.4.14",
"typescript": "^5"
}
}

View File

@ -1,8 +0,0 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;

View File

@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 391 B

View File

@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 385 B

View File

@ -1,11 +0,0 @@
import BaseLayout from "@/components/Layout/BaseLayout";
import { routing } from "@/i18n/routing";
import { ReactNode } from "react";
type Props = {
children: ReactNode;
};
export default function RootLayout({ children }: Props) {
return <BaseLayout locale={routing.defaultLocale}>{children}</BaseLayout>;
}

View File

@ -1,9 +0,0 @@
const LoginLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-100">
{children}
</div>
);
};
export default LoginLayout;

View File

@ -1,74 +0,0 @@
"use client";
import BackButton from "@/components/Buttons/BackButton";
import StartpageButton from "@/components/Buttons/StartpageButton";
import Link from "next/link";
import React from "react";
import { useState } from "react";
const LoginPage = () => {
const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log("Email:", email);
console.log("Password:", password);
};
return (
<div className="w-full max-w-md bg-white rounded-lg shadow-md p-8">
<h2 className="text-2xl font-bold mb-6 text-center">Login</h2>
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label
htmlFor="email"
className="block text-gray-700 font-medium mb-2"
>
Email
</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Enter your email"
required
/>
</div>
<div className="mb-4">
<label
htmlFor="password"
className="block text-gray-700 font-medium mb-2"
>
Password
</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Enter your password"
required
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 rounded-lg hover:bg-blue-600 transition duration-200"
>
Login
</button>
<Link className="block text-center mt-4 text-blue-500" href="register">
Create an account
</Link>
<div className="flex justify-between mt-8">
<BackButton />
<StartpageButton />
</div>
</form>
</div>
);
};
export default LoginPage;

View File

@ -1,9 +0,0 @@
const LoginLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-100">
{children}
</div>
);
};
export default LoginLayout;

View File

@ -1,108 +0,0 @@
"use client";
import Link from "next/link";
import React, { FormEvent, useState } from "react";
import BackButton from "@/components/Buttons/BackButton";
import StartpageButton from "@/components/Buttons/StartpageButton";
const RegisterPage = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
interface RegisterFormState {
email: string;
password: string;
confirmPassword: string;
}
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
if (password !== confirmPassword) {
alert("Passwords do not match");
return;
}
const formData: RegisterFormState = {
email,
password,
confirmPassword,
};
// Registrierungshandhabung hier hinzufügen
console.log("Registering user with data", formData);
};
return (
<div className="flex items-center justify-center min-h-screen bg-gray-100">
<div className="w-full max-w-md bg-white rounded-lg shadow-md p-8">
<h2 className="text-2xl font-bold mb-6 text-center">Registrieren</h2>
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label
htmlFor="email"
className="block text-gray-700 font-medium mb-2"
>
Email
</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Geben Sie Ihre Email ein"
required
/>
</div>
<div className="mb-4">
<label
htmlFor="password"
className="block text-gray-700 font-medium mb-2"
>
Passwort
</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Geben Sie Ihr Passwort ein"
required
/>
</div>
<div className="mb-4">
<label
htmlFor="confirmPassword"
className="block text-gray-700 font-medium mb-2"
>
Passwort bestätigen
</label>
<input
type="password"
id="confirmPassword"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Bestätigen Sie Ihr Passwort"
required
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 rounded-lg hover:bg-blue-600 transition duration-200"
>
Registrieren
</button>
<Link className="block text-center mt-4 text-blue-500" href="/login">
Bereits ein Konto? Anmelden
</Link>
<div className="flex justify-between mt-8">
<BackButton />
<StartpageButton />
</div>
</form>
</div>
</div>
);
};
export default RegisterPage;

View File

@ -1,16 +0,0 @@
import { ReactNode } from "react";
import BaseLayout from "@/components/Layout/BaseLayout";
import { routing } from "@/i18n/routing";
import UnauthenticatedLayout from "@/components/Layout/UnauthenticatedLayout";
type Props = {
children: ReactNode;
};
export default function RootLayout({ children }: Props) {
return (
<BaseLayout locale={routing.defaultLocale}>
<UnauthenticatedLayout>{children}</UnauthenticatedLayout>
</BaseLayout>
);
}

View File

@ -1,48 +0,0 @@
"use client";
import React from "react";
export default function About() {
return (
<main className="flex items-center justify-center">
<div className="max-w-2xl pt-5 sm:pt-10 lg:pt-20">
<div className="text-center">
<h1 className="text-balance text-5xl font-semibold tracking-tight text-gray-900 sm:text-7xl">
Über das Projekt
</h1>
<p className="mt-8 text-pretty text-lg font-medium text-gray-500 sm:text-xl/8">
Entwickler: Jean Jacques Avril
</p>
<p className="mt-2 text-pretty text-lg font-medium text-gray-500 sm:text-xl/8">
Webseite:{" "}
<a
href="https://jeanavril.com"
className="text-indigo-600 hover:underline"
>
jeanavril.com
</a>
</p>
<p className="mt-2 text-pretty text-lg font-medium text-gray-500 sm:text-xl/8">
Technologien: Next.js, Go, Docker, Deno, Dart
</p>
<p className="mt-8 text-pretty text-lg font-medium text-gray-500 sm:text-xl/8">
Mit ActaTempus können Sie Ihre Arbeitszeiten mühelos erfassen und
Einblicke in Ihre Arbeitsgewohnheiten gewinnen. Beginnen Sie noch
heute und steigern Sie Ihre Produktivität.
</p>
<div className="mt-10 flex items-center justify-center gap-x-6">
<a
href="/register"
className="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Registrieren
</a>
<a href="#" className="text-sm/6 font-semibold text-gray-900">
Mehr erfahren <span aria-hidden="true"></span>
</a>
</div>
</div>
</div>
</main>
);
}

View File

@ -1,16 +0,0 @@
import { ReactNode } from "react";
import BaseLayout from "@/components/Layout/BaseLayout";
import { routing } from "@/i18n/routing";
import UnauthenticatedLayout from "@/components/Layout/UnauthenticatedLayout";
type Props = {
children: ReactNode;
};
export default function RootLayout({ children }: Props) {
return (
<BaseLayout locale={routing.defaultLocale}>
<UnauthenticatedLayout>{children}</UnauthenticatedLayout>
</BaseLayout>
);
}

View File

@ -1,36 +0,0 @@
"use client";
import Link from "next/link";
export default function Home() {
return (
<main className="flex items-center justify-center">
<div className="max-w-2xl pt-5 sm:pt-10 lg:pt-20">
<div className="text-center">
<h1 className="text-balance text-5xl font-semibold tracking-tight text-gray-900 sm:text-7xl">
Track your working hours with ease
</h1>
<p className="mt-8 text-pretty text-lg font-medium text-gray-500 sm:text-xl/8">
With ActaTempus, you can easily track your working hours and get
insights into your work habits. Get started today and be more
productive.
</p>
<div className="mt-10 flex items-center justify-center gap-x-6">
<a
href="/register"
className="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Register
</a>
<a href="#" className="text-sm/6 font-semibold text-gray-900">
Learn more <span aria-hidden="true"></span>
</a>
<Link href={"/dashboard"}>
Go to Dashboard
</Link>
</div>
</div>
</div>
</main>
);
}

View File

@ -1,19 +0,0 @@
import { ReactNode } from "react";
import BaseLayout from "@/components/Layout/BaseLayout";
import { routing } from "@/i18n/routing";
import UnauthenticatedLayout from "@/components/Layout/UnauthenticatedLayout";
import AuthGuard from "@/components/guards/AuthGuard";
type Props = {
children: ReactNode;
};
export default function RootLayout({ children }: Props) {
return (
<BaseLayout locale={routing.defaultLocale}>
<AuthGuard>
<UnauthenticatedLayout>{children}</UnauthenticatedLayout>
</AuthGuard>
</BaseLayout>
);
}

View File

@ -1,97 +0,0 @@
"use client";
import React, { FormEvent, useState } from "react";
const Dashboard = () => {
const [newEntry, setNewEntry] = useState({ task: "", duration: "" });
const [entries, setEntries] = useState([
{ id: 1, task: "Meeting", duration: "1h 30m", date: "2024-12-30" },
{ id: 2, task: "Development", duration: "3h 45m", date: "2024-12-29" },
{ id: 3, task: "Code Review", duration: "2h", date: "2024-12-28" },
]);
const handleInputChange = (e: FormEvent) => {
const { name, value } = e.target as HTMLInputElement;
setNewEntry({ ...newEntry, [name]: value });
};
const handleAddEntry = () => {
if (!newEntry.task || !newEntry.duration) return;
const newId = entries.length ? entries[entries.length - 1].id + 1 : 1;
setEntries([
...entries,
{ id: newId, ...newEntry, date: new Date().toISOString().split("T")[0] },
]);
setNewEntry({ task: "", duration: "" });
};
return (
<main className="min-h-screen bg-gray-100 py-10 px-4">
<div className="max-w-4xl mx-auto bg-white rounded-lg shadow-lg p-6">
{/* Timer Section */}
<section className="mb-8">
<h2 className="text-2xl font-bold text-gray-800 mb-4">New Entry</h2>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<input
type="text"
name="task"
value={newEntry.task}
onChange={handleInputChange}
placeholder="Task Name"
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500 focus:outline-none"
/>
<input
type="text"
name="duration"
value={newEntry.duration}
onChange={handleInputChange}
placeholder="Duration (e.g., 1h 30m)"
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500 focus:outline-none"
/>
<button
onClick={handleAddEntry}
className="w-full bg-indigo-600 text-white py-2 rounded-lg hover:bg-indigo-500 transition duration-200"
>
Add Entry
</button>
</div>
</section>
{/* Past Entries Section */}
<section>
<h2 className="text-2xl font-bold text-gray-800 mb-4">
Past Entries
</h2>
<div className="bg-gray-50 p-4 rounded-lg shadow-inner">
{entries.length
? (
<ul className="divide-y divide-gray-300">
{entries.map((entry) => (
<li
key={entry.id}
className="py-3 flex justify-between items-center"
>
<div>
<p className="text-lg font-medium text-gray-700">
{entry.task}
</p>
<p className="text-sm text-gray-500">
{entry.date} - {entry.duration}
</p>
</div>
</li>
))}
</ul>
)
: (
<p className="text-gray-500">
No entries yet. Start adding some!
</p>
)}
</div>
</section>
</div>
</main>
);
};
export default Dashboard;

View File

@ -1,16 +0,0 @@
import { UserProvider } from "@/context/UserContext";
import { ReactNode } from "react";
type Props = {
children: ReactNode;
};
// Since we have a `not-found.tsx` page on the root, a layout file
// is required, even if it's just passing children through.
export default function RootLayout({ children }: Props) {
return (
<UserProvider>
{children}
</UserProvider>
);
}

View File

@ -1,15 +0,0 @@
import BaseLayout from '@/components/Layout/BaseLayout';
import Error404Page from '@/components/ErrorPages/404';
import {routing} from '@/i18n/routing';
// This page renders when a route like `/unknown.txt` is requested.
// In this case, the layout at `app/[locale]/layout.tsx` receives
// an invalid value as the `[locale]` param and calls `notFound()`.
export default function GlobalNotFound() {
return (
<BaseLayout locale={routing.defaultLocale}>
<Error404Page />
</BaseLayout>
);
}

View File

@ -1,6 +0,0 @@
import {redirect} from 'next/navigation';
// This page only renders when the app is built statically (output: 'export')
export default function RootPage() {
redirect('/en');
}

View File

@ -1,26 +0,0 @@
import { ChevronLeft } from "lucide-react";
import { useRouter } from "next/navigation";
const BackButton = () => {
const router = useRouter();
const handleBack = () => {
if (window.history.length > 1) {
router.back();
} else {
router.push("/");
}
};
return (
<button
onClick={handleBack}
className="text-blue-500 hover:underline focus:outline-none flex items-center"
>
<ChevronLeft className="inline-block w-4 h-4 mr-2" />
Zurück
</button>
);
};
export default BackButton;

View File

@ -1,16 +0,0 @@
import { HomeIcon } from "lucide-react";
import Link from "next/link";
const StartpageButton = () => {
return (
<Link
className="text-blue-500 hover:underline focus:outline-none flex items-center"
href="/"
>
<HomeIcon className="inline-block w-4 h-4 mr-2" />
Startseite
</Link>
);
};
export default StartpageButton;

View File

@ -1,11 +0,0 @@
import { useTranslations } from 'next-intl';
import ErrorPageLayout from './ErrorPageLayout';
export default function Error404Page() {
const t = useTranslations('NotFoundPage');
return (
<ErrorPageLayout errorCode={404} errorMessage={t('title')}>
<p className="text-center mt-4">{t('description')}</p>
</ErrorPageLayout>
);
}

View File

@ -1,15 +0,0 @@
"use client"; // Kennzeichnet die Komponente als Client-Komponente
type BackButtonProps = {
label: string;
};
export default function BackButton({ label }: BackButtonProps) {
return (
<button
onClick={() => window.history.back()}
className="inline-flex px-6 py-3 bg-cyan-500 hover:bg-cyan-600 text-white font-semibold rounded-lg shadow-md">
{label}
</button>
);
}

View File

@ -1,41 +0,0 @@
import { ThemeProvider } from "@/context/ThemeContext";
import { AlertCircle } from "lucide-react";
import { useTranslations } from "next-intl";
import { ReactNode } from "react";
import BackButton from "./BackButton";
import HomeButton from "./HomeButton";
type ErrorPageLayoutProps = {
children: ReactNode;
errorCode?: number;
errorMessage?: string;
};
export default function ErrorPageLayout({
children,
errorCode,
errorMessage,
}: ErrorPageLayoutProps) {
const t = useTranslations("Error");
return (
<ThemeProvider>
<div className="min-h-screen flex flex-col items-center justify-center bg-slate-100 dark:bg-gray-900 text-gray-900 dark:text-white">
<div className="flex flex-col items-center p-8">
<AlertCircle className="h-16 w-16 text-red-600 mb-4" />
<h1 className="text-5xl font-bold mb-4">
{errorCode || t("defaultErrorCode")}
</h1>
<p className="text-xl mb-6">
{errorMessage || t("defaultErrorMessage")}
</p>
<div className="row space-x-4">
<HomeButton label={t("backToHome")} />
<BackButton label={t("back")} />
</div>
</div>
<div className="mt-4">{children}</div>
</div>
</ThemeProvider>
);
}

View File

@ -1,15 +0,0 @@
import Link from "next/link";
type HomeButtonProps = {
label: string;
};
export default function HomeButton({ label }: HomeButtonProps) {
return (
<Link
href="/"
className="inline-flex px-6 py-3 bg-cyan-500 hover:bg-cyan-600 text-white hover:text-white font-semibold rounded-lg shadow-md">
{label}
</Link>
);
}

View File

@ -1,29 +0,0 @@
import { ReactNode } from "react";
import { useRouter } from "next/router";
import { useEffect } from "react";
import Header from "./Header/Header";
type AuthenticatedLayoutProps = {
children: ReactNode;
};
export default function AuthenticatedLayout({
children,
}: AuthenticatedLayoutProps) {
const router = useRouter();
useEffect(() => {
// Beispiel zur Überprüfung der Authentifizierung; ersetze dies mit deiner Authentifizierungslogik
const isAuthenticated = true; // Hier eine echte Überprüfung einfügen
if (!isAuthenticated) {
router.push("/login");
}
}, [router]);
return (
<div className="min-h-screen flex flex-col bg-slate-100 dark:bg-slate-900">
<Header />
<main className="flex-grow p-4">{children}</main>
</div>
);
}

View File

@ -1,44 +0,0 @@
import { ThemeProvider } from "@/context/ThemeContext";
import "@/styles/globals.scss";
import type { Metadata } from "next";
import { NextIntlClientProvider } from "next-intl";
import { getMessages } from "next-intl/server";
import localFont from "next/font/local";
import { ReactNode } from "react";
const geistSans = localFont({
src: "./fonts/GeistVF.woff",
variable: "--font-geist-sans",
weight: "100 900",
});
const geistMono = localFont({
src: "./fonts/GeistMonoVF.woff",
variable: "--font-geist-mono",
weight: "100 900",
});
export const metadata: Metadata = {
title: "Time Tracker",
description: "Time Tracker",
};
type Props = {
children: ReactNode;
locale: string;
};
export default async function BaseLayout({ children, locale }: Props) {
const messages = await getMessages();
return (
<html lang={locale}>
<NextIntlClientProvider messages={messages}>
<ThemeProvider>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased `}>
{children}
</body>
</ThemeProvider>
</NextIntlClientProvider>
</html>
);
}

View File

@ -1,24 +0,0 @@
"use client";
import LocaleSwitcher from "../Navigation/LocaleSwitcher";
import NavigationBar from "../Navigation/NavigationBar";
import { ThemeSwitcher } from "../Navigation/ThemeSwitcher";
const Header: React.FC = () => {
return (
<header className="flex justify-between items-center p-4 bg-white dark:bg-gray-900 shadow-md">
{/* Logo */}
<div className="text-xl font-bold text-gray-900 dark:text-white">
ActaTempus
</div>
<NavigationBar />
{/* Theme Switcher Dropdown */}
<div className="flex items-center">
<ThemeSwitcher />
<LocaleSwitcher />
</div>
</header>
);
};
export default Header;

View File

@ -1,19 +0,0 @@
import {useLocale, useTranslations} from 'next-intl';
import {routing} from '@/i18n/routing';
import LocaleSwitcherSelect from './LocaleSwitcherSelect';
export default function LocaleSwitcher() {
const t = useTranslations('LocaleSwitcher');
const locale = useLocale();
return (
<LocaleSwitcherSelect defaultValue={locale} label={t('label')}>
{routing.locales.map((cur) => (
<option key={cur} value={cur}>
{t('locale', {locale: cur})}
</option>
))}
</LocaleSwitcherSelect>
);
}

View File

@ -1,48 +0,0 @@
"use client";
import { Locale, usePathname, useRouter } from "@/i18n/routing";
import { useParams } from "next/navigation";
import { ChangeEvent, ReactNode, useTransition } from "react";
type Props = {
children: ReactNode;
defaultValue: string;
label: string;
};
export default function LocaleSwitcherSelect({
children,
defaultValue,
label,
}: Props) {
const router = useRouter();
const [isPending, startTransition] = useTransition();
const pathname = usePathname();
const params = useParams();
function onSelectChange(event: ChangeEvent<HTMLSelectElement>) {
const nextLocale = event.target.value as Locale;
startTransition(() => {
router.replace(
// @ts-expect-error -- TypeScript will validate that only known `params`
// are used in combination with a given `pathname`. Since the two will
// always match for the current route, we can skip runtime checks.
{ pathname, params },
{ locale: nextLocale }
);
});
}
return (
<label className="relative text-gray-400">
<p className="sr-only">{label}</p>
<select
className="inline-flex appearance-none bg-transparent py-3 pl-2 pr-6"
defaultValue={defaultValue}
disabled={isPending}
onChange={onSelectChange}>
{children}
</select>
</label>
);
}

View File

@ -1,45 +0,0 @@
import { Menu, X } from "lucide-react";
import { useTranslations } from "next-intl";
import { useState } from "react";
export default function NavigationBar() {
const t = useTranslations("Navigation");
const [menuOpen, setMenuOpen] = useState<boolean>(false);
return (
<>
{/* Menu Button for Mobile */}
<div className="md:hidden">
<button onClick={() => setMenuOpen(!menuOpen)}>
{menuOpen ? (
<X className="h-6 w-6 text-gray-900 dark:text-white" />
) : (
<Menu className="h-6 w-6 text-gray-900 dark:text-white" />
)}
</button>
</div>
{/* Navigation Links */}
<nav
className={`md:flex ${
menuOpen ? "block" : "hidden"
} md:block space-x-4`}>
<a
href="/"
className="text-gray-900 dark:text-white hover:text-blue-500">
{t("home")}
</a>
<a
href="/about"
className="text-gray-900 dark:text-white hover:text-blue-500">
About
</a>
<a
href="/contact"
className="text-gray-900 dark:text-white hover:text-blue-500">
Contact
</a>
</nav>
</>
);
}

View File

@ -1,27 +0,0 @@
'use client';
import clsx from 'clsx';
import {useSelectedLayoutSegment} from 'next/navigation';
import {ComponentProps} from 'react';
import {Link} from '@/i18n/routing';
export default function NavigationLink({
href,
...rest
}: ComponentProps<typeof Link>) {
const selectedLayoutSegment = useSelectedLayoutSegment();
const pathname = selectedLayoutSegment ? `/${selectedLayoutSegment}` : '/';
const isActive = pathname === href;
return (
<Link
aria-current={isActive ? 'page' : undefined}
className={clsx(
'inline-block px-2 py-3 transition-colors',
isActive ? 'text-white' : 'text-gray-400 hover:text-gray-200'
)}
href={href}
{...rest}
/>
);
}

View File

@ -1,48 +0,0 @@
import { useTheme } from "@/context/ThemeContext";
import { Monitor, Sun, Moon } from "lucide-react";
import { useState } from "react";
export function ThemeSwitcher() {
const { setTheme } = useTheme();
const [dropdownOpen, setDropdownOpen] = useState<boolean>(false);
const handleThemeChange = (newTheme: string) => {
setTheme(newTheme);
setDropdownOpen(false);
};
return (
<div className="relative">
<button
onClick={() => setDropdownOpen(!dropdownOpen)}
className="flex items-center space-x-2 p-2 rounded hover:bg-gray-300 dark:hover:bg-gray-600">
<Monitor className="h-5 w-5 text-gray-800 dark:text-gray-200" />
</button>
{dropdownOpen && (
<div className="absolute right-0 mt-2 w-40 bg-white dark:bg-gray-800 rounded shadow-lg z-10">
<ul>
<li
onClick={() => handleThemeChange("light")}
className={`cursor-pointer px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-white`}>
<Sun className="inline-block h-5 w-5 mr-2 text-yellow-500" />{" "}
Light
</li>
<li
onClick={() => handleThemeChange("dark")}
className={`cursor-pointer px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-white`}>
<Moon className="inline-block h-5 w-5 mr-2 text-blue-500" /> Dark
</li>
<li
onClick={() => handleThemeChange("system")}
className={`cursor-pointer px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-white`}>
<Monitor className="inline-block h-5 w-5 mr-2 text-gray-800 dark:text-gray-200" />{" "}
Default
</li>
</ul>
</div>
)}
</div>
);
}

View File

@ -1,17 +0,0 @@
import { ReactNode } from 'react';
import Header from './Header/Header';
type UnauthenticatedLayoutProps = {
children: ReactNode;
};
export default function UnauthenticatedLayout({ children }: UnauthenticatedLayoutProps) {
return (
<div className="min-h-screen flex flex-col bg-white dark:bg-gray-800">
<Header />
<main className="flex-grow p-4">
{children}
</main>
</div>
);
}

View File

@ -1,23 +0,0 @@
"use client";
import { useRouter } from "next/navigation";
import { useUser } from "@/context/UserContext";
import React, { useEffect } from "react";
const AuthGuard = ({ children }: { children: React.ReactNode }) => {
const router = useRouter();
const { user } = useUser(); // Benutzer aus dem globalen Zustand abrufen
useEffect(() => {
if (!user) {
router.push("/login"); // Weiterleitung zur Login-Seite
}
}, [user, router]);
if (!user) {
return <p>Lade...</p>; // Anzeige während des Ladens oder Weiterleitens
}
return <>{children}</>;
};
export default AuthGuard;

View File

@ -1,56 +0,0 @@
"use client";
import React, { createContext, useContext, useEffect, useState } from 'react';
type ThemeContextType = {
theme: string;
setTheme: (theme: string) => void;
};
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState<string>('system');
useEffect(() => {
const savedTheme = localStorage.getItem('theme') || 'system';
setTheme(savedTheme);
applyTheme(savedTheme);
}, []);
const applyTheme = (selectedTheme: string) => {
if (selectedTheme === 'dark') {
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
} else if (selectedTheme === 'light') {
document.documentElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
} else {
localStorage.removeItem('theme');
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (systemPrefersDark) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}
};
const handleThemeChange = (newTheme: string) => {
setTheme(newTheme);
applyTheme(newTheme);
};
return (
<ThemeContext.Provider value={{ theme, setTheme: handleThemeChange }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};

View File

@ -1,33 +0,0 @@
"use client";
import React, { createContext, ReactNode, useContext, useState } from "react";
type User = {
id: string;
name: string;
email: string;
};
type UserContextType = {
user: User | null;
setUser: (user: User | null) => void;
};
const UserContext = createContext<UserContextType | undefined>(undefined);
export const UserProvider = ({ children }: { children: ReactNode }) => {
const [user, setUser] = useState<User | null>(null);
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
};
export const useUser = (): UserContextType => {
const context = useContext(UserContext);
if (!context) {
throw new Error("useUser must be used within a UserProvider");
}
return context;
};

View File

@ -1,18 +0,0 @@
import {getRequestConfig} from 'next-intl/server';
import {routing} from './routing';
export default getRequestConfig(async ({requestLocale}) => {
// This typically corresponds to the `[locale]` segment
let locale = await requestLocale;
type LocaleType = (typeof routing.locales)[number];
// Ensure that a valid locale is used
if (!locale || !routing.locales.includes(locale as LocaleType)) {
locale = routing.defaultLocale;
}
return {
locale,
messages: (await import(`../../messages/${locale}.json`)).default
};
});

View File

@ -1,20 +0,0 @@
import { createNavigation } from "next-intl/navigation";
import { defineRouting } from "next-intl/routing";
export const routing = defineRouting({
locales: ["en", "de"],
defaultLocale: "en",
pathnames: {
"/": "/",
"/pathnames": {
en: "/pathnames",
de: "/pfadnamen",
},
},
});
export type Pathnames = keyof typeof routing.pathnames;
export type Locale = (typeof routing.locales)[number];
export const { Link, getPathname, redirect, usePathname, useRouter } =
createNavigation(routing);

View File

@ -1,23 +0,0 @@
import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";
export default createMiddleware(routing);
export const config = {
// Match only internationalized pathnames
matcher: [
// Enable a redirect to a matching locale at the root
"/",
// Set a cookie to remember the previous locale for
// all requests that have a locale prefix
"/(de|en)/:path*",
// Enable redirects that add missing locales
// (e.g. `/pathnames` -> `/en/pathnames`)
//'/((?!_next|_vercel|.*\\..*).*)'
// Ausschließen von API-Routen
"/((?!api|_next|_vercel|.*\\..*).*)",
],
};

View File

@ -1,42 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--background: #ffffff;
--foreground: #171717;
--primary-color: #1d4ed8; /* Primärfarbe für Akzente */
--secondary-color: #3b82f6; /* Sekundärfarbe für Hover-Effekte */
--border-color: #d1d5db; /* Farbe für Ränder */
--shadow-color: rgba(0, 0, 0, 0.1); /* Schattenfarbe */
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
--primary-color: #2563eb; /* Angepasste Primärfarbe für den Dunkelmodus */
--secondary-color: #60a5fa; /* Angepasste Sekundärfarbe */
--border-color: #374151; /* Angepasste Farbe für Ränder */
--shadow-color: rgba(0, 0, 0, 0.4); /* Stärkere Schattenfarbe */
}
}
body {
//color: var(--foreground);
//background: var(--background);
font-family: Arial, Helvetica, sans-serif;
margin: 0;
padding: 0;
line-height: 1.6; /* Verbesserte Lesbarkeit */
}
a {
color: var(--primary-color);
text-decoration: none;
transition: color 0.3s;
&:hover {
color: var(--secondary-color);
}
}

View File

@ -1,23 +0,0 @@
import type { Config } from "tailwindcss";
export default {
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
darkMode: "selector",
theme: {
extend: {
colors: {
//background: "var(--background)",
//foreground: "var(--foreground)",
//primary: "var(--primary-color)",
//secondary: "var(--secondary-color)",
//border: "var(--border-color)",
//shadow: "var(--shadow-color)",
},
},
},
plugins: [],
} satisfies Config;

View File

@ -1,27 +0,0 @@
{
"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": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}