feat(auth): Add Oauth configuration for Telegram, Facebook, Google, Github, and Apple

This commit is contained in:
web@ppanel
2025-01-19 20:43:35 +07:00
parent cefcb310d6
commit 18ee600836
133 changed files with 2391 additions and 24 deletions
+4 -3
View File
@@ -60,10 +60,11 @@
"cmdk": "1.0.4",
"date-fns": "^4.1.0",
"embla-carousel-react": "^8.5.2",
"framer-motion": "^11.16.1",
"framer-motion": "^11.18.1",
"input-otp": "^1.4.2",
"lucide-react": "^0.469.0",
"lucide-react": "^0.473.0",
"mathjs": "^14.0.1",
"motion": "^11.18.1",
"next-themes": "^0.4.4",
"react-day-picker": "8.10.1",
"react-hook-form": "^7.54.2",
@@ -77,7 +78,7 @@
"remark-math": "^6.0.0",
"remark-toc": "^9.0.0",
"rtl-detect": "^1.1.2",
"sonner": "^1.7.1",
"sonner": "^1.7.2",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
"vaul": "^1.1.2",
+133
View File
@@ -0,0 +1,133 @@
'use client';
import { cn } from '@workspace/ui/lib/utils';
import { AnimatePresence, motion, MotionProps } from 'motion/react';
import { useEffect, useRef, useState } from 'react';
type CharacterSet = string[] | readonly string[];
interface HyperTextProps extends MotionProps {
/** The text content to be animated */
children: string;
/** Optional className for styling */
className?: string;
/** Duration of the animation in milliseconds */
duration?: number;
/** Delay before animation starts in milliseconds */
delay?: number;
/** Component to render as - defaults to div */
as?: React.ElementType;
/** Whether to start animation when element comes into view */
startOnView?: boolean;
/** Whether to trigger animation on hover */
animateOnHover?: boolean;
/** Custom character set for scramble effect. Defaults to uppercase alphabet */
characterSet?: CharacterSet;
}
const DEFAULT_CHARACTER_SET = Object.freeze(
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
) as readonly string[];
const getRandomInt = (max: number): number => Math.floor(Math.random() * max);
export default function HyperText({
children,
className,
duration = 800,
delay = 0,
as: Component = 'div',
startOnView = false,
animateOnHover = true,
characterSet = DEFAULT_CHARACTER_SET,
...props
}: HyperTextProps) {
const MotionComponent = motion.create(Component, {
forwardMotionProps: true,
});
const [displayText, setDisplayText] = useState<string[]>(() => children.split(''));
const [isAnimating, setIsAnimating] = useState(false);
const iterationCount = useRef(0);
const elementRef = useRef<HTMLElement>(null);
const handleAnimationTrigger = () => {
if (animateOnHover && !isAnimating) {
iterationCount.current = 0;
setIsAnimating(true);
}
};
// Handle animation start based on view or delay
useEffect(() => {
if (!startOnView) {
const startTimeout = setTimeout(() => {
setIsAnimating(true);
}, delay);
return () => clearTimeout(startTimeout);
}
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setTimeout(() => {
setIsAnimating(true);
}, delay);
observer.disconnect();
}
},
{ threshold: 0.1, rootMargin: '-30% 0px -30% 0px' },
);
if (elementRef.current) {
observer.observe(elementRef.current);
}
return () => observer.disconnect();
}, [delay, startOnView]);
// Handle scramble animation
useEffect(() => {
if (!isAnimating) return;
const intervalDuration = duration / (children.length * 10);
const maxIterations = children.length;
const interval = setInterval(() => {
if (iterationCount.current < maxIterations) {
setDisplayText((currentText) =>
currentText.map((letter, index) =>
letter === ' '
? letter
: index <= iterationCount.current
? children[index]
: characterSet[getRandomInt(characterSet.length)],
),
);
iterationCount.current = iterationCount.current + 0.1;
} else {
setIsAnimating(false);
clearInterval(interval);
}
}, intervalDuration);
return () => clearInterval(interval);
}, [children, duration, isAnimating, characterSet]);
return (
<MotionComponent
ref={elementRef}
className={cn('overflow-hidden py-2 text-4xl font-bold', className)}
onMouseEnter={handleAnimationTrigger}
{...props}
>
<AnimatePresence>
{displayText.map((letter, index) => (
<motion.span key={index} className={cn('font-mono', letter === ' ' ? 'w-3' : '')}>
{letter.toUpperCase()}
</motion.span>
))}
</AnimatePresence>
</MotionComponent>
);
}
@@ -0,0 +1,70 @@
import { cn } from '@workspace/ui/lib/utils';
import React from 'react';
export interface OrbitingCirclesProps extends React.HTMLAttributes<HTMLDivElement> {
className?: string;
children?: React.ReactNode;
reverse?: boolean;
duration?: number;
delay?: number;
radius?: number;
path?: boolean;
iconSize?: number;
speed?: number;
}
export function OrbitingCircles({
className,
children,
reverse,
duration = 20,
radius = 160,
path = true,
iconSize = 30,
speed = 1,
...props
}: OrbitingCirclesProps) {
const calculatedDuration = duration / speed;
return (
<>
{path && (
<svg
xmlns='http://www.w3.org/2000/svg'
version='1.1'
className='pointer-events-none absolute inset-0 size-full'
>
<circle
className='stroke-black/10 stroke-1 dark:stroke-white/10'
cx='50%'
cy='50%'
r={radius}
fill='none'
/>
</svg>
)}
{React.Children.map(children, (child, index) => {
const angle = (360 / React.Children.count(children)) * index;
return (
<div
style={
{
'--duration': calculatedDuration,
'--radius': radius,
'--angle': angle,
'--icon-size': `${iconSize}px`,
} as React.CSSProperties
}
className={cn(
`animate-orbit absolute flex size-[var(--icon-size)] transform-gpu items-center justify-center rounded-full`,
{ '[animation-direction:reverse]': reverse },
className,
)}
{...props}
>
{child}
</div>
);
})}
</>
);
}
+11
View File
@@ -92,10 +92,21 @@ const config = {
height: '0',
},
},
'orbit': {
'0%': {
transform:
'rotate(calc(var(--angle) * 1deg)) translateY(calc(var(--radius) * 1px)) rotate(calc(var(--angle) * -1deg))',
},
'100%': {
transform:
'rotate(calc(var(--angle) * 1deg + 360deg)) translateY(calc(var(--radius) * 1px)) rotate(calc((var(--angle) * -1deg) - 360deg))',
},
},
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
'orbit': 'orbit calc(var(--duration)*1s) linear infinite',
},
},
},