migrating

This commit is contained in:
Michael Freno
2025-12-17 00:23:13 -05:00
parent b3df3eedd2
commit 81969ae907
79 changed files with 4187 additions and 172 deletions

View File

@@ -0,0 +1,73 @@
import { Component, createEffect, onCleanup } from "solid-js";
interface CountdownCircleTimerProps {
duration: number;
initialRemainingTime: number;
size: number;
strokeWidth: number;
colors: string;
children: () => any;
}
const CountdownCircleTimer: Component<CountdownCircleTimerProps> = (props) => {
const radius = (props.size - props.strokeWidth) / 2;
const circumference = radius * 2 * Math.PI;
// Calculate progress (0 to 1)
const progress = () => props.initialRemainingTime / props.duration;
const strokeDashoffset = () => circumference * (1 - progress());
return (
<div
style={{
position: "relative",
width: `${props.size}px`,
height: `${props.size}px`,
}}
>
<svg
width={props.size}
height={props.size}
style={{ transform: "rotate(-90deg)" }}
>
{/* Background circle */}
<circle
cx={props.size / 2}
cy={props.size / 2}
r={radius}
fill="none"
stroke="#e5e7eb"
stroke-width={props.strokeWidth}
/>
{/* Progress circle */}
<circle
cx={props.size / 2}
cy={props.size / 2}
r={radius}
fill="none"
stroke={props.colors}
stroke-width={props.strokeWidth}
stroke-dasharray={circumference}
stroke-dashoffset={strokeDashoffset()}
stroke-linecap="round"
style={{
transition: "stroke-dashoffset 0.5s linear",
}}
/>
</svg>
{/* Timer text in center */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
}}
>
{props.children()}
</div>
</div>
);
};
export default CountdownCircleTimer;

View File

@@ -0,0 +1,36 @@
import { Component } from "solid-js";
interface EyeProps {
strokeWidth: number;
height: number;
width: number;
class?: string;
}
const Eye: Component<EyeProps> = (props) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={props.strokeWidth}
stroke="currentColor"
height={props.height}
width={props.width}
class={props.class}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
);
};
export default Eye;

View File

@@ -0,0 +1,31 @@
import { Component } from "solid-js";
interface EyeSlashProps {
strokeWidth: number;
height: number;
width: number;
class?: string;
}
const EyeSlash: Component<EyeSlashProps> = (props) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={props.strokeWidth}
stroke="currentColor"
height={props.height}
width={props.width}
class={props.class}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"
/>
</svg>
);
};
export default EyeSlash;

View File

@@ -0,0 +1,24 @@
import { Component } from "solid-js";
interface GitHubProps {
height?: string | number;
width?: string | number;
fill?: string;
}
const GitHub: Component<GitHubProps> = (props) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 496 512"
height={props.height}
width={props.width}
fill={props.fill || ""}
class={props.fill ? "" : "fill-black dark:fill-white"}
>
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" />
</svg>
);
};
export default GitHub;

View File

@@ -0,0 +1,37 @@
import { Component } from "solid-js";
interface GoogleLogoProps {
height: number;
width: number;
}
const GoogleLogo: Component<GoogleLogoProps> = (props) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height={props.height}
viewBox="0 0 24 24"
width={props.width}
>
<path
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
fill="#4285F4"
/>
<path
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
fill="#34A853"
/>
<path
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
fill="#FBBC05"
/>
<path
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
fill="#EA4335"
/>
<path d="M1 1h22v22H1z" fill="none" />
</svg>
);
};
export default GoogleLogo;

View File

@@ -0,0 +1,90 @@
import { JSX, splitProps, Show } from "solid-js";
export interface ButtonProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary" | "danger" | "ghost";
size?: "sm" | "md" | "lg";
loading?: boolean;
fullWidth?: boolean;
}
/**
* Reusable button component with variants and loading state
*/
export default function Button(props: ButtonProps) {
const [local, others] = splitProps(props, [
"variant",
"size",
"loading",
"fullWidth",
"class",
"children",
"disabled",
]);
const variant = () => local.variant || "primary";
const size = () => local.size || "md";
const baseClasses = "flex justify-center items-center rounded font-semibold transition-all duration-300 ease-out disabled:opacity-50 disabled:cursor-not-allowed";
const variantClasses = () => {
switch (variant()) {
case "primary":
return "bg-blue-400 hover:bg-blue-500 active:scale-90 dark:bg-blue-600 dark:hover:bg-blue-700 text-white shadow-lg shadow-blue-300 dark:shadow-blue-700";
case "secondary":
return "bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-900 dark:text-white";
case "danger":
return "bg-red-500 hover:bg-red-600 active:scale-90 text-white shadow-lg shadow-red-300 dark:shadow-red-700";
case "ghost":
return "bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-700 dark:text-gray-300";
default:
return "";
}
};
const sizeClasses = () => {
switch (size()) {
case "sm":
return "px-3 py-1.5 text-sm";
case "md":
return "px-4 py-2 text-base";
case "lg":
return "px-6 py-3 text-lg";
default:
return "";
}
};
const widthClass = () => (local.fullWidth ? "w-full" : "");
return (
<button
{...others}
disabled={local.disabled || local.loading}
class={`${baseClasses} ${variantClasses()} ${sizeClasses()} ${widthClass()} ${local.class || ""}`}
>
<Show when={local.loading} fallback={local.children}>
<svg
class="animate-spin h-5 w-5 mr-2"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
Loading...
</Show>
</button>
);
}

View File

@@ -0,0 +1,45 @@
import { JSX, splitProps } from "solid-js";
export interface InputProps extends JSX.InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
helperText?: string;
}
/**
* Reusable input component with label and error handling
* Styled to match Next.js migration source (underlined input style)
*/
export default function Input(props: InputProps) {
const [local, others] = splitProps(props, ["label", "error", "helperText", "class"]);
return (
<div class="input-group">
<input
{...others}
placeholder=" "
class={`underlinedInput bg-transparent ${local.class || ""}`}
aria-invalid={!!local.error}
aria-describedby={local.error ? `${others.id}-error` : undefined}
/>
<span class="bar"></span>
{local.label && (
<label class="underlinedInputLabel">{local.label}</label>
)}
{local.error && (
<span
id={`${others.id}-error`}
class="text-xs text-red-500 mt-1 block"
role="alert"
>
{local.error}
</span>
)}
{local.helperText && !local.error && (
<span class="text-xs text-gray-500 mt-1 block">
{local.helperText}
</span>
)}
</div>
);
}