feat: onboarding trial flow, Google sign-in, dark mode hotspot fixes

- Move trial creation from auth hook to dedicated /subscriptions/trial endpoint
  with eligibility checks (3-day Full Paket trial)
- Add animated onboarding progress (Remotion) with confetti on completion
- Register redirects to subscription page with welcome flow
- Add Google social login/signup support with config injection
- Improve hotspot overlay visibility in dark mode
- Show "Panele Git" on landing page when authenticated
- Turkish translations for API error messages
- Add toast utility wrapper, UUID generation for auth IDs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sase Dev
2026-02-15 14:53:57 +00:00
parent a5ee147675
commit 87d8eea298
50 changed files with 1116 additions and 298 deletions

View File

@@ -0,0 +1,238 @@
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
} from "remotion";
function getColors(isDark: boolean) {
return {
bg: isDark ? "#1a1a1a" : "#f5f5f5",
fg: isDark ? "#fafafa" : "#0a0a0a",
muted: isDark ? "#262626" : "#e5e5e5",
mutedFg: isDark ? "#a3a3a3" : "#737373",
border: isDark ? "#333333" : "#d4d4d4",
emerald: "#10b981",
emeraldDark: "#059669",
surface: isDark ? "#1f1f1f" : "#ffffff",
trackBg: isDark ? "#262626" : "#e5e5e5",
checkBg: isDark ? "#1f1f1f" : "#ffffff",
};
}
const STEPS = [
{ label: "Hesap doğrulanıyor", endFrame: 60 },
{ label: "Kataloglar hazırlanıyor", endFrame: 120 },
{ label: "Full Paket aktif ediliyor", endFrame: 175 },
{ label: "Tamamlandı!", endFrame: 210 },
];
export const OnboardingProgress: React.FC<{
isDark: boolean;
stepLabels?: string[];
}> = ({ isDark, stepLabels }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const c = getColors(isDark);
const labels = stepLabels ?? STEPS.map((s) => s.label);
// Overall progress 0→1 over 210 frames with ease-in acceleration
const rawProgress = interpolate(frame, [0, 210], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const progress = rawProgress * rawProgress * (3 - 2 * rawProgress); // smoothstep
const barWidth = 600;
const barHeight = 8;
const barX = 100;
const barY = 100;
// Determine active step index
const activeStepIndex = STEPS.findIndex((s) => frame < s.endFrame);
const currentStep = activeStepIndex === -1 ? STEPS.length - 1 : activeStepIndex;
return (
<AbsoluteFill
style={{
backgroundColor: "transparent",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontFamily: "system-ui, -apple-system, sans-serif",
}}
>
{/* Active step label */}
<div
style={{
position: "absolute",
top: barY - 48,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
}}
>
{labels.map((label, i) => {
const isActive = i === currentStep;
const labelOpacity = isActive
? spring({
frame: frame - Math.max(0, i === 0 ? 0 : STEPS[i - 1].endFrame),
fps,
config: { damping: 20, stiffness: 120 },
})
: 0;
return (
<div
key={i}
style={{
position: "absolute",
opacity: labelOpacity,
fontSize: 16,
fontWeight: 600,
color: i === STEPS.length - 1 && isActive ? c.emerald : c.fg,
whiteSpace: "nowrap",
}}
>
{label}
</div>
);
})}
</div>
{/* Track background */}
<div
style={{
position: "absolute",
top: barY,
left: barX,
width: barWidth,
height: barHeight,
borderRadius: barHeight / 2,
backgroundColor: c.trackBg,
}}
/>
{/* Track fill */}
<div
style={{
position: "absolute",
top: barY,
left: barX,
width: barWidth * progress,
height: barHeight,
borderRadius: barHeight / 2,
background: `linear-gradient(90deg, ${c.emerald}, ${c.emeraldDark})`,
}}
/>
{/* Checkpoints */}
{STEPS.map((step, i) => {
const stepX = barX + (barWidth / (STEPS.length - 1)) * i;
const stepY = barY + barHeight / 2;
const reached = frame >= step.endFrame;
const reaching = frame >= (i === 0 ? 0 : STEPS[i - 1].endFrame);
const fillProgress = reached
? 1
: reaching
? spring({
frame: frame - (i === 0 ? 0 : STEPS[i - 1].endFrame),
fps,
config: { damping: 12, stiffness: 100 },
durationInFrames: step.endFrame - (i === 0 ? 0 : STEPS[i - 1].endFrame),
})
: 0;
const checkScale = reached
? spring({
frame: frame - step.endFrame,
fps,
config: { damping: 10, stiffness: 200 },
})
: 0;
const circleSize = 28;
return (
<div key={i}>
{/* Outer circle */}
<div
style={{
position: "absolute",
left: stepX - circleSize / 2,
top: stepY - circleSize / 2,
width: circleSize,
height: circleSize,
borderRadius: "50%",
backgroundColor: c.checkBg,
border: `2px solid ${fillProgress > 0 ? c.emerald : c.border}`,
display: "flex",
alignItems: "center",
justifyContent: "center",
overflow: "hidden",
}}
>
{/* Fill background */}
<div
style={{
position: "absolute",
inset: 0,
backgroundColor: c.emerald,
opacity: fillProgress,
borderRadius: "50%",
}}
/>
{/* Checkmark */}
<svg
viewBox="0 0 24 24"
width={14}
height={14}
style={{
position: "relative",
zIndex: 1,
transform: `scale(${checkScale})`,
}}
>
<path
d="M5 13l4 4L19 7"
fill="none"
stroke="white"
strokeWidth={3}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
{/* Step number below (visible when not yet reached) */}
{!reached && (
<div
style={{
position: "absolute",
left: stepX - circleSize / 2,
top: stepY - circleSize / 2,
width: circleSize,
height: circleSize,
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 11,
fontWeight: 700,
color: fillProgress > 0.5 ? "white" : c.mutedFg,
zIndex: 2,
opacity: 1 - checkScale,
}}
>
{i + 1}
</div>
)}
</div>
);
})}
</AbsoluteFill>
);
};