@korioinc/next-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +90 -0
  3. package/dist/ads/components/AdContainer.d.ts +12 -0
  4. package/dist/ads/components/AdContainer.d.ts.map +1 -0
  5. package/dist/ads/components/AdContainer.js +287 -0
  6. package/dist/ads/components/GoogleAdSense.d.ts +6 -0
  7. package/dist/ads/components/GoogleAdSense.d.ts.map +1 -0
  8. package/dist/ads/components/GoogleAdSense.js +6 -0
  9. package/dist/ads/components/MockAdDisplay.d.ts +8 -0
  10. package/dist/ads/components/MockAdDisplay.d.ts.map +1 -0
  11. package/dist/ads/components/MockAdDisplay.js +13 -0
  12. package/dist/ads/config.d.ts +3 -0
  13. package/dist/ads/config.d.ts.map +1 -0
  14. package/dist/ads/config.js +134 -0
  15. package/dist/ads/index.d.ts +6 -0
  16. package/dist/ads/index.d.ts.map +1 -0
  17. package/dist/ads/index.js +7 -0
  18. package/dist/ads/types.d.ts +15 -0
  19. package/dist/ads/types.d.ts.map +1 -0
  20. package/dist/ads/types.js +1 -0
  21. package/dist/api-client/index.d.ts +31 -0
  22. package/dist/api-client/index.d.ts.map +1 -0
  23. package/dist/api-client/index.js +90 -0
  24. package/dist/auth/auth-manager.d.ts +45 -0
  25. package/dist/auth/auth-manager.d.ts.map +1 -0
  26. package/dist/auth/auth-manager.js +75 -0
  27. package/dist/auth/index.client.d.ts +7 -0
  28. package/dist/auth/index.client.d.ts.map +1 -0
  29. package/dist/auth/index.client.js +5 -0
  30. package/dist/auth/index.d.ts +8 -0
  31. package/dist/auth/index.d.ts.map +1 -0
  32. package/dist/auth/index.js +11 -0
  33. package/dist/auth/index.server.d.ts +7 -0
  34. package/dist/auth/index.server.d.ts.map +1 -0
  35. package/dist/auth/index.server.js +5 -0
  36. package/dist/auth/providers/auth-context-provider.d.ts +21 -0
  37. package/dist/auth/providers/auth-context-provider.d.ts.map +1 -0
  38. package/dist/auth/providers/auth-context-provider.js +74 -0
  39. package/dist/auth/routing.d.ts +3 -0
  40. package/dist/auth/routing.d.ts.map +1 -0
  41. package/dist/auth/routing.js +40 -0
  42. package/dist/auth/types/auth.d.ts +23 -0
  43. package/dist/auth/types/auth.d.ts.map +1 -0
  44. package/dist/auth/types/auth.js +1 -0
  45. package/dist/auth/types/user.d.ts +6 -0
  46. package/dist/auth/types/user.d.ts.map +1 -0
  47. package/dist/auth/types/user.js +1 -0
  48. package/dist/auth/utils/auth.d.ts +32 -0
  49. package/dist/auth/utils/auth.d.ts.map +1 -0
  50. package/dist/auth/utils/auth.js +116 -0
  51. package/dist/auth/utils/permission.d.ts +29 -0
  52. package/dist/auth/utils/permission.d.ts.map +1 -0
  53. package/dist/auth/utils/permission.js +44 -0
  54. package/dist/components/head-manifest/index.d.ts +6 -0
  55. package/dist/components/head-manifest/index.d.ts.map +1 -0
  56. package/dist/components/head-manifest/index.js +4 -0
  57. package/dist/components/index.d.ts +6 -0
  58. package/dist/components/index.d.ts.map +1 -0
  59. package/dist/components/index.js +7 -0
  60. package/dist/components/json-ld/index.d.ts +7 -0
  61. package/dist/components/json-ld/index.d.ts.map +1 -0
  62. package/dist/components/json-ld/index.js +6 -0
  63. package/dist/components/locale-switcher/_components/china-flag.d.ts +2 -0
  64. package/dist/components/locale-switcher/_components/china-flag.d.ts.map +1 -0
  65. package/dist/components/locale-switcher/_components/china-flag.js +4 -0
  66. package/dist/components/locale-switcher/_components/japan-flag.d.ts +2 -0
  67. package/dist/components/locale-switcher/_components/japan-flag.d.ts.map +1 -0
  68. package/dist/components/locale-switcher/_components/japan-flag.js +4 -0
  69. package/dist/components/locale-switcher/_components/korea-flag.d.ts +4 -0
  70. package/dist/components/locale-switcher/_components/korea-flag.d.ts.map +1 -0
  71. package/dist/components/locale-switcher/_components/korea-flag.js +4 -0
  72. package/dist/components/locale-switcher/_components/taiwan-flag.d.ts +2 -0
  73. package/dist/components/locale-switcher/_components/taiwan-flag.d.ts.map +1 -0
  74. package/dist/components/locale-switcher/_components/taiwan-flag.js +4 -0
  75. package/dist/components/locale-switcher/_components/usa-flag.d.ts +4 -0
  76. package/dist/components/locale-switcher/_components/usa-flag.d.ts.map +1 -0
  77. package/dist/components/locale-switcher/_components/usa-flag.js +4 -0
  78. package/dist/components/locale-switcher/index.d.ts +11 -0
  79. package/dist/components/locale-switcher/index.d.ts.map +1 -0
  80. package/dist/components/locale-switcher/index.js +69 -0
  81. package/dist/components/theme-switcher/index.d.ts +3 -0
  82. package/dist/components/theme-switcher/index.d.ts.map +1 -0
  83. package/dist/components/theme-switcher/index.js +20 -0
  84. package/dist/components/tooltip/index.d.ts +8 -0
  85. package/dist/components/tooltip/index.d.ts.map +1 -0
  86. package/dist/components/tooltip/index.js +40 -0
  87. package/dist/i18n/init-lingui.d.ts +7 -0
  88. package/dist/i18n/init-lingui.d.ts.map +1 -0
  89. package/dist/i18n/init-lingui.js +7 -0
  90. package/dist/i18n/lingui.setup.d.ts +15 -0
  91. package/dist/i18n/lingui.setup.d.ts.map +1 -0
  92. package/dist/i18n/lingui.setup.js +29 -0
  93. package/dist/i18n/providers/lingui-client-provider.d.ts +9 -0
  94. package/dist/i18n/providers/lingui-client-provider.d.ts.map +1 -0
  95. package/dist/i18n/providers/lingui-client-provider.js +14 -0
  96. package/dist/i18n/routing.d.ts +25 -0
  97. package/dist/i18n/routing.d.ts.map +1 -0
  98. package/dist/i18n/routing.js +252 -0
  99. package/dist/local-storage/index.d.ts +4 -0
  100. package/dist/local-storage/index.d.ts.map +1 -0
  101. package/dist/local-storage/index.js +4 -0
  102. package/dist/local-storage/local-storage-manager.d.ts +41 -0
  103. package/dist/local-storage/local-storage-manager.d.ts.map +1 -0
  104. package/dist/local-storage/local-storage-manager.js +200 -0
  105. package/dist/local-storage/valtio-persist.d.ts +31 -0
  106. package/dist/local-storage/valtio-persist.d.ts.map +1 -0
  107. package/dist/local-storage/valtio-persist.js +84 -0
  108. package/dist/navigation/index.d.ts +4 -0
  109. package/dist/navigation/index.d.ts.map +1 -0
  110. package/dist/navigation/index.js +4 -0
  111. package/dist/navigation/permissions.d.ts +25 -0
  112. package/dist/navigation/permissions.d.ts.map +1 -0
  113. package/dist/navigation/permissions.js +97 -0
  114. package/dist/navigation/types.d.ts +57 -0
  115. package/dist/navigation/types.d.ts.map +1 -0
  116. package/dist/navigation/types.js +1 -0
  117. package/dist/navigation/utils.d.ts +42 -0
  118. package/dist/navigation/utils.d.ts.map +1 -0
  119. package/dist/navigation/utils.js +107 -0
  120. package/dist/utils/cookie.d.ts +3 -0
  121. package/dist/utils/cookie.d.ts.map +1 -0
  122. package/dist/utils/cookie.js +22 -0
  123. package/dist/utils/helpers.d.ts +5 -0
  124. package/dist/utils/helpers.d.ts.map +1 -0
  125. package/dist/utils/helpers.js +18 -0
  126. package/dist/utils/index.d.ts +3 -0
  127. package/dist/utils/index.d.ts.map +1 -0
  128. package/dist/utils/index.js +2 -0
  129. package/dist/utils/sitemap.d.ts +13 -0
  130. package/dist/utils/sitemap.d.ts.map +1 -0
  131. package/dist/utils/sitemap.js +19 -0
  132. package/package.json +126 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Korio Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # @korioinc/next-core
2
+
3
+ Core utilities and components for Next.js applications.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @korioinc/next-core
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **Authentication**: Complete auth system with JWT support
14
+ - **Internationalization**: i18n routing and locale management
15
+ - **Components**: Reusable UI components (Theme switcher, Locale switcher)
16
+ - **Ads Management**: Ad container components and configurations
17
+ - **API Client**: Configured fetch client with interceptors
18
+ - **Navigation**: Type-safe navigation system
19
+ - **Local Storage**: Enhanced local storage management
20
+ - **Utilities**: Common utility functions
21
+
22
+ ## Usage
23
+
24
+ ### Authentication
25
+
26
+ ```typescript
27
+ import { useAuthContext } from '@korioinc/next-core/auth/client';
28
+ import { getCurrentUser } from '@korioinc/next-core/auth/server';
29
+
30
+ // Client-side
31
+ const { isLogin, user, login, logout } = useAuthContext();
32
+
33
+ // Server-side
34
+ const user = await getCurrentUser();
35
+ ```
36
+
37
+ ### Internationalization
38
+
39
+ ```typescript
40
+ import { handleLocaleRouting } from '@korioinc/next-core/i18n/routing';
41
+
42
+ // In middleware.ts
43
+ export default function middleware(request: NextRequest) {
44
+ return handleLocaleRouting(request);
45
+ }
46
+ ```
47
+
48
+ ### Components
49
+
50
+ ```typescript
51
+ import { ThemeSwitcher, LocaleSwitcher } from '@korioinc/next-core/components';
52
+
53
+ export function Header() {
54
+ return (
55
+ <header>
56
+ <ThemeSwitcher />
57
+ <LocaleSwitcher />
58
+ </header>
59
+ );
60
+ }
61
+ ```
62
+
63
+ ### Ads
64
+
65
+ ```typescript
66
+ import { AdContainer } from '@korioinc/next-core/ads';
67
+
68
+ export function Page() {
69
+ return (
70
+ <AdContainer
71
+ adType="leaderboard"
72
+ className="my-4"
73
+ />
74
+ );
75
+ }
76
+ ```
77
+
78
+ ## Requirements
79
+
80
+ - Next.js 15+
81
+ - React 19+
82
+ - TypeScript 5.9+
83
+
84
+ ## License
85
+
86
+ MIT
87
+
88
+ ## Contributing
89
+
90
+ Contributions are welcome! Please feel free to submit a Pull Request.
@@ -0,0 +1,12 @@
1
+ import { AdContainerProps } from '../../ads/types';
2
+ declare global {
3
+ interface Window {
4
+ adsbygoogle: Array<{
5
+ push?: (params: Record<string, unknown>) => void;
6
+ loaded?: boolean;
7
+ [key: string]: unknown;
8
+ }>;
9
+ }
10
+ }
11
+ export default function AdContainer({ adType, className }: AdContainerProps): import("react/jsx-runtime").JSX.Element | null;
12
+ //# sourceMappingURL=AdContainer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AdContainer.d.ts","sourceRoot":"","sources":["../../../src/ads/components/AdContainer.tsx"],"names":[],"mappings":"AAQA,OAAO,EAAY,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AA0B7D,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,WAAW,EAAE,KAAK,CAAC;YACjB,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;YACjD,MAAM,CAAC,EAAE,OAAO,CAAC;YACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;SACxB,CAAC,CAAC;KACJ;CACF;AA+ED,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAAE,MAAM,EAAE,SAAc,EAAE,EAAE,gBAAgB,kDA2P/E"}
@@ -0,0 +1,287 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useEffect, useRef } from 'react';
4
+ import { usePathname } from 'next/navigation';
5
+ import MockAdDisplay from '../../ads/components/MockAdDisplay';
6
+ import { AD_CONFIGS } from '../../ads/config';
7
+ import { cn } from '../../utils/helpers';
8
+ import { getConfig } from '@korioinc/next-conf';
9
+ // Constants for ad loading configuration
10
+ const AD_LOADING_CONFIG = {
11
+ MAX_RETRIES: 10,
12
+ RETRY_DELAY_MS: 200,
13
+ INITIAL_DELAY_MS: 100,
14
+ MIN_CONTAINER_WIDTH: 250,
15
+ CONTAINER_CHECK_DELAY_MS: 500,
16
+ SCRIPT_LOAD_RETRY_DELAY_MS: 1000,
17
+ INTERSECTION_THRESHOLD: 0.51,
18
+ };
19
+ // Ad status constants
20
+ const AD_STATUS = {
21
+ DONE: 'done',
22
+ };
23
+ // Ad formats
24
+ const AD_FORMAT = {
25
+ RECTANGLE: 'rectangle',
26
+ AUTO: 'auto',
27
+ };
28
+ // Helper function to check if environment is development
29
+ const isDevelopmentEnvironment = (config, publisherId) => {
30
+ return (!publisherId ||
31
+ !config.slotId ||
32
+ process.env.NEXT_PUBLIC_APP_ENV === 'local' ||
33
+ process.env.NEXT_PUBLIC_APP_ENV === 'development');
34
+ };
35
+ // Helper function to clean up ad element
36
+ const cleanupAdElement = (container) => {
37
+ if (!container)
38
+ return;
39
+ const adElement = container.querySelector('ins.adsbygoogle');
40
+ if (!adElement)
41
+ return;
42
+ // More thorough cleanup
43
+ adElement.innerHTML = '';
44
+ adElement.removeAttribute('data-adsbygoogle-status');
45
+ adElement.removeAttribute('data-ad-status');
46
+ // Clone to remove any event listeners
47
+ const clonedElement = adElement.cloneNode(true);
48
+ adElement.parentNode?.replaceChild(clonedElement, adElement);
49
+ };
50
+ // Helper function to create ad element
51
+ const createAdElement = (config, adInstanceId, publisherId) => {
52
+ const ins = document.createElement('ins');
53
+ ins.className = 'adsbygoogle';
54
+ ins.setAttribute('data-ad-client', `ca-pub-${publisherId}`);
55
+ ins.setAttribute('data-ad-slot', config.slotId);
56
+ if (config.layout === 'in-article') {
57
+ ins.setAttribute('data-ad-format', AD_FORMAT.RECTANGLE);
58
+ }
59
+ ins.setAttribute('data-ad-instance-id', adInstanceId);
60
+ // Apply inline styles as per Google AdSense guidelines
61
+ ins.style.display = 'inline-block';
62
+ ins.style.width = `${config.width}px`;
63
+ ins.style.height = `${config.height}px`;
64
+ return ins;
65
+ };
66
+ // Helper function to check if container is valid for ad loading
67
+ const isContainerValid = (container) => {
68
+ if (!container)
69
+ return false;
70
+ // Check if element is visible
71
+ if (window.getComputedStyle(container).display === 'none') {
72
+ return false;
73
+ }
74
+ // Check if ins element already exists and is loaded
75
+ const existingIns = container.querySelector('ins.adsbygoogle');
76
+ if (existingIns?.getAttribute('data-adsbygoogle-status') === AD_STATUS.DONE) {
77
+ return false;
78
+ }
79
+ return true;
80
+ };
81
+ // Helper function to push ad to adsbygoogle
82
+ const pushAdToGoogle = () => {
83
+ if (window.adsbygoogle) {
84
+ (window.adsbygoogle = window.adsbygoogle || []).push({});
85
+ return true;
86
+ }
87
+ return false;
88
+ };
89
+ export default function AdContainer({ adType, className = '' }) {
90
+ const config = AD_CONFIGS[adType];
91
+ if (!config) {
92
+ return null;
93
+ }
94
+ // Get publisher ID from config
95
+ const adsensePublisherId = getConfig().analytics?.adsensePublisherId;
96
+ // Check if dev environment early - check this BEFORE checking publisherId
97
+ const isDevEnvironment = isDevelopmentEnvironment(config, adsensePublisherId);
98
+ // Only check publisherId for production environment
99
+ if (!isDevEnvironment && !adsensePublisherId) {
100
+ return null;
101
+ }
102
+ // Get alignment class for Tailwind
103
+ const getAlignmentClass = () => {
104
+ if (config.alignment === 'left')
105
+ return 'justify-start';
106
+ if (config.alignment === 'right')
107
+ return 'justify-end';
108
+ return 'justify-center';
109
+ };
110
+ // Dev environment: Return mock ad without any refs or hooks
111
+ if (isDevEnvironment) {
112
+ // Two divs: outer for size reservation (layout shift prevention), inner for flex alignment
113
+ const outerStyle = {
114
+ minHeight: `${config.height}px`,
115
+ '--ad-width': `${config.width}px`,
116
+ '--ad-height': `${config.height}px`,
117
+ };
118
+ const innerStyle = {
119
+ display: 'flex',
120
+ justifyContent: config.alignment === 'left' ? 'flex-start' : config.alignment === 'right' ? 'flex-end' : 'center',
121
+ alignItems: 'center',
122
+ };
123
+ return (_jsx("div", { className: cn('relative', className), style: outerStyle, children: _jsx("div", { style: innerStyle, children: _jsx(MockAdDisplay, { adType: adType, config: config }) }) }));
124
+ }
125
+ // Production-only setup (refs and hooks only created when needed)
126
+ const containerRef = useRef(null);
127
+ const pathname = usePathname();
128
+ const adInstanceRef = useRef(false);
129
+ const retryTimerRef = useRef(null);
130
+ const intersectionObserverRef = useRef(null);
131
+ // Unique identifier for debugging and tracking
132
+ const adInstanceId = adType;
133
+ useEffect(() => {
134
+ // Reset on route change
135
+ adInstanceRef.current = false;
136
+ // Clean up existing ads and timers
137
+ if (retryTimerRef.current) {
138
+ clearTimeout(retryTimerRef.current);
139
+ retryTimerRef.current = null;
140
+ }
141
+ cleanupAdElement(containerRef.current);
142
+ }, [pathname]);
143
+ useEffect(() => {
144
+ if (adInstanceRef.current)
145
+ return;
146
+ // Helper function to handle container width validation
147
+ const validateContainerWidth = () => {
148
+ const containerWidth = containerRef.current?.offsetWidth || 0;
149
+ if (containerWidth === 0) {
150
+ retryTimerRef.current = setTimeout(() => {
151
+ if (!adInstanceRef.current && containerRef.current) {
152
+ const retryWidth = containerRef.current.offsetWidth;
153
+ if (retryWidth > 0) {
154
+ loadAd();
155
+ }
156
+ }
157
+ }, AD_LOADING_CONFIG.CONTAINER_CHECK_DELAY_MS);
158
+ return null;
159
+ }
160
+ // For in-article ads, ensure minimum width
161
+ if (config.layout === 'in-article' && containerWidth < AD_LOADING_CONFIG.MIN_CONTAINER_WIDTH) {
162
+ console.warn(`AdSense: Container too narrow for fluid ads (${containerWidth}px). Using fixed size.`);
163
+ }
164
+ return containerWidth;
165
+ };
166
+ // Helper function to handle existing ad element
167
+ const handleExistingAdElement = () => {
168
+ const existingIns = containerRef.current?.querySelector('ins.adsbygoogle');
169
+ if (!existingIns)
170
+ return true;
171
+ // Check if ad is already loaded
172
+ if (existingIns.getAttribute('data-adsbygoogle-status') === AD_STATUS.DONE) {
173
+ return false;
174
+ }
175
+ // Remove existing ins if it exists but hasn't loaded
176
+ existingIns.remove();
177
+ return true;
178
+ };
179
+ // Helper function to initialize ad with AdSense
180
+ const initializeAd = () => {
181
+ if (!window.adsbygoogle) {
182
+ // Retry after a delay
183
+ retryTimerRef.current = setTimeout(() => {
184
+ if (window.adsbygoogle && containerRef.current?.querySelector('ins.adsbygoogle')) {
185
+ if (pushAdToGoogle()) {
186
+ adInstanceRef.current = true;
187
+ }
188
+ }
189
+ }, AD_LOADING_CONFIG.SCRIPT_LOAD_RETRY_DELAY_MS);
190
+ return;
191
+ }
192
+ // Now push will only process this newly created ins element
193
+ if (pushAdToGoogle()) {
194
+ adInstanceRef.current = true;
195
+ // Disconnect observer after successful load
196
+ if (intersectionObserverRef.current) {
197
+ intersectionObserverRef.current.disconnect();
198
+ intersectionObserverRef.current = null;
199
+ }
200
+ }
201
+ };
202
+ const loadAd = () => {
203
+ try {
204
+ // Validate container
205
+ if (!isContainerValid(containerRef.current)) {
206
+ return;
207
+ }
208
+ // Handle existing ad element
209
+ if (!handleExistingAdElement()) {
210
+ return;
211
+ }
212
+ // Validate container width
213
+ const containerWidth = validateContainerWidth();
214
+ if (containerWidth === null) {
215
+ return;
216
+ }
217
+ // Create and append ad element (publisherId is guaranteed to exist in production)
218
+ if (!adsensePublisherId) {
219
+ console.warn('AdSense: Publisher ID is missing in production environment');
220
+ return;
221
+ }
222
+ const ins = createAdElement(config, adInstanceId, adsensePublisherId);
223
+ containerRef.current?.appendChild(ins);
224
+ // Initialize ad with AdSense
225
+ initializeAd();
226
+ }
227
+ catch (err) {
228
+ console.error('AdSense loading error:', {
229
+ adType,
230
+ error: err,
231
+ adInstanceId,
232
+ containerWidth: containerRef.current?.offsetWidth || 0,
233
+ });
234
+ }
235
+ };
236
+ // Set up Intersection Observer to load ad when threshold is met
237
+ if (containerRef.current) {
238
+ intersectionObserverRef.current = new IntersectionObserver((entries) => {
239
+ entries.forEach((entry) => {
240
+ if (entry.isIntersecting &&
241
+ entry.intersectionRatio >= AD_LOADING_CONFIG.INTERSECTION_THRESHOLD &&
242
+ !adInstanceRef.current) {
243
+ // Container is at least threshold% visible, load the ad
244
+ loadAd();
245
+ }
246
+ });
247
+ }, {
248
+ threshold: [AD_LOADING_CONFIG.INTERSECTION_THRESHOLD],
249
+ });
250
+ intersectionObserverRef.current.observe(containerRef.current);
251
+ }
252
+ return () => {
253
+ // Clean up observer
254
+ if (intersectionObserverRef.current) {
255
+ intersectionObserverRef.current.disconnect();
256
+ intersectionObserverRef.current = null;
257
+ }
258
+ // Clean up timer
259
+ if (retryTimerRef.current) {
260
+ clearTimeout(retryTimerRef.current);
261
+ retryTimerRef.current = null;
262
+ }
263
+ // Clean up dynamically created elements
264
+ if (containerRef.current) {
265
+ const insElement = containerRef.current.querySelector('ins.adsbygoogle');
266
+ if (insElement && insElement.getAttribute('data-adsbygoogle-status') !== AD_STATUS.DONE) {
267
+ insElement.remove();
268
+ }
269
+ }
270
+ // Reset instance flag for Strict Mode
271
+ adInstanceRef.current = false;
272
+ };
273
+ }, [pathname]);
274
+ // Production environment: Return container for ads
275
+ // Use two divs structure: outer for size reservation (layout shift prevention), inner for flex alignment and ad insertion
276
+ const outerStyle = {
277
+ minHeight: `${config.height}px`,
278
+ '--ad-width': `${config.width}px`,
279
+ '--ad-height': `${config.height}px`,
280
+ };
281
+ const innerStyle = {
282
+ display: 'flex',
283
+ justifyContent: config.alignment === 'left' ? 'flex-start' : config.alignment === 'right' ? 'flex-end' : 'center',
284
+ alignItems: 'center',
285
+ };
286
+ return (_jsx("div", { className: cn('relative', className), style: outerStyle, children: _jsx("div", { ref: containerRef, style: innerStyle }) }));
287
+ }
@@ -0,0 +1,6 @@
1
+ interface GoogleAdSenseProps {
2
+ publisherId: string;
3
+ }
4
+ export default function GoogleAdSense({ publisherId }: GoogleAdSenseProps): import("react/jsx-runtime").JSX.Element;
5
+ export {};
6
+ //# sourceMappingURL=GoogleAdSense.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GoogleAdSense.d.ts","sourceRoot":"","sources":["../../../src/ads/components/GoogleAdSense.tsx"],"names":[],"mappings":"AAIA,UAAU,kBAAkB;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EAAE,WAAW,EAAE,EAAE,kBAAkB,2CASxE"}
@@ -0,0 +1,6 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import Script from 'next/script';
4
+ export default function GoogleAdSense({ publisherId }) {
5
+ return (_jsx(Script, { async: true, src: `https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-${publisherId}`, crossOrigin: 'anonymous', strategy: 'lazyOnload' }));
6
+ }
@@ -0,0 +1,8 @@
1
+ import { AdConfig, AdType } from '../../ads/types';
2
+ interface MockAdDisplayProps {
3
+ adType: AdType;
4
+ config: AdConfig;
5
+ }
6
+ export default function MockAdDisplay({ adType, config }: MockAdDisplayProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
8
+ //# sourceMappingURL=MockAdDisplay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockAdDisplay.d.ts","sourceRoot":"","sources":["../../../src/ads/components/MockAdDisplay.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEnD,UAAU,kBAAkB;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,QAAQ,CAAC;CAClB;AAED,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,kBAAkB,2CAqB3E"}
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export default function MockAdDisplay({ adType, config }) {
3
+ // Mock ad box styles
4
+ const mockAdStyles = {
5
+ width: `${config.width}px`,
6
+ height: `${config.height}px`,
7
+ maxWidth: '100%',
8
+ display: 'flex',
9
+ alignItems: 'center',
10
+ justifyContent: 'center',
11
+ };
12
+ return (_jsx("div", { className: 'border-border bg-secondary relative border-2 border-dashed', style: mockAdStyles, children: _jsxs("div", { className: 'text-center', children: [_jsx("p", { className: 'text-muted-foreground font-semibold', children: adType }), _jsxs("p", { className: 'text-muted-foreground text-sm', children: [config.width, " x ", config.height] })] }) }));
13
+ }
@@ -0,0 +1,3 @@
1
+ import { AdConfig, AdType } from '../ads/types';
2
+ export declare const AD_CONFIGS: Partial<Record<AdType, AdConfig>>;
3
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/ads/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEnD,eAAO,MAAM,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CA4IxD,CAAC"}
@@ -0,0 +1,134 @@
1
+ export const AD_CONFIGS = {
2
+ // Sticky 좌우 광고
3
+ // 좌측: left-rail-1, left-rail-2 세로로 순서대로 배치, 우측정렬
4
+ // 우측: right-rail-1, right-rail-2, 좌측정렬
5
+ 'left-rail-1': {
6
+ slotId: '4944477902',
7
+ width: 300,
8
+ height: 250,
9
+ layout: 'display',
10
+ alignment: 'right',
11
+ },
12
+ 'left-rail-2': {
13
+ slotId: '6756807933',
14
+ width: 300,
15
+ height: 600,
16
+ layout: 'display',
17
+ alignment: 'right',
18
+ },
19
+ 'right-rail-1': {
20
+ slotId: '6134254736',
21
+ width: 300,
22
+ height: 250,
23
+ layout: 'display',
24
+ alignment: 'left',
25
+ },
26
+ 'right-rail-2': {
27
+ slotId: '5299701120',
28
+ width: 300,
29
+ height: 600,
30
+ layout: 'display',
31
+ alignment: 'left',
32
+ },
33
+ // 페이지 최상단 광고
34
+ // 모든 페이지의 컨텐츠 최상단에 GNB 바로 아래에 있는 광고는 leaderboard
35
+ // in-feed 면 안됨
36
+ leaderboard: {
37
+ slotId: '7112316569',
38
+ width: 728,
39
+ height: 90,
40
+ layout: 'display',
41
+ },
42
+ // 페이지 중간중간에 삽입되는 가로로 길쭉한 광고
43
+ // in-feed-1 부터 5까지
44
+ // 동일한 숫자의 in feed 가 중복으로 보여지면 안됨. 항상 1부터 순서대로 유니크하게 보여야함
45
+ 'in-feed-1': {
46
+ slotId: '1627624393',
47
+ width: 728,
48
+ height: 90,
49
+ layout: 'in-article',
50
+ },
51
+ 'in-feed-2': {
52
+ slotId: '4328991117',
53
+ width: 728,
54
+ height: 90,
55
+ layout: 'in-article',
56
+ },
57
+ 'in-feed-3': {
58
+ slotId: '3937290639',
59
+ width: 728,
60
+ height: 90,
61
+ layout: 'in-article',
62
+ },
63
+ 'in-feed-4': {
64
+ slotId: '3015909448',
65
+ width: 728,
66
+ height: 90,
67
+ layout: 'in-article',
68
+ },
69
+ 'in-feed-5': {
70
+ slotId: '2903410560',
71
+ width: 728,
72
+ height: 90,
73
+ layout: 'in-article',
74
+ },
75
+ // 페이지 중간중간에 가로가 짧은 단에 삽입되는 정사각형 광고
76
+ // in-feed-left-1 또는 in-feed-right-1
77
+ // left-rail-1 이나 right rail 은 쓰면 안됨
78
+ 'in-feed-left-1': {
79
+ slotId: '4024920541',
80
+ width: 300,
81
+ height: 250,
82
+ layout: 'in-article',
83
+ },
84
+ 'in-feed-right-1': {
85
+ slotId: '5945325110',
86
+ width: 300,
87
+ height: 250,
88
+ layout: 'in-article',
89
+ },
90
+ // 페이지 맨 하단에 나오는 광고
91
+ // 페이지 맨 하단에 푸터 상단에 있는 광고는 항상 footer 여야함
92
+ // in-feed 쓰면 안됨
93
+ footer: {
94
+ slotId: '1576078538',
95
+ width: 970,
96
+ height: 250,
97
+ layout: 'display',
98
+ },
99
+ // 매우 큰 광고
100
+ // 홈인 경우: home-billboard
101
+ // 홈 아닌경우: billboard
102
+ 'home-billboard': {
103
+ slotId: '9277247223',
104
+ width: 970,
105
+ height: 250,
106
+ layout: 'display',
107
+ },
108
+ billboard: {
109
+ slotId: '9614572420',
110
+ width: 970,
111
+ height: 250,
112
+ layout: 'display',
113
+ },
114
+ // 페이지 제일 상단에 있는 광고 (정사각형)
115
+ 'mobile-unit-1': {
116
+ slotId: '2376910921',
117
+ width: 300,
118
+ height: 250,
119
+ layout: 'display',
120
+ },
121
+ // 페이지 제일 하단에 있는 광고
122
+ 'mobile-unit-2': {
123
+ slotId: '9238946133',
124
+ width: 300,
125
+ height: 250,
126
+ layout: 'display',
127
+ },
128
+ 'mobile-in-feed-1': {
129
+ slotId: '7925864461',
130
+ width: 300,
131
+ height: 250,
132
+ layout: 'in-article',
133
+ },
134
+ };
@@ -0,0 +1,6 @@
1
+ export { default as GoogleAdSense } from './components/GoogleAdSense';
2
+ export { default as AdContainer } from './components/AdContainer';
3
+ export { default as MockAdDisplay } from './components/MockAdDisplay';
4
+ export * from './types';
5
+ export * from './config';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ads/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAGtE,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC"}
@@ -0,0 +1,7 @@
1
+ // Components
2
+ export { default as GoogleAdSense } from './components/GoogleAdSense';
3
+ export { default as AdContainer } from './components/AdContainer';
4
+ export { default as MockAdDisplay } from './components/MockAdDisplay';
5
+ // Types and configuration
6
+ export * from './types';
7
+ export * from './config';
@@ -0,0 +1,15 @@
1
+ export type AdLayout = 'display' | 'in-article';
2
+ export type AdType = 'left-rail-1' | 'left-rail-2' | 'right-rail-1' | 'right-rail-2' | 'leaderboard' | 'in-feed-1' | 'in-feed-2' | 'in-feed-3' | 'in-feed-4' | 'in-feed-5' | 'in-feed-left-1' | 'in-feed-right-1' | 'footer' | 'home-billboard' | 'billboard' | 'mobile-unit-1' | 'mobile-unit-2' | 'mobile-in-feed-1';
3
+ export type AdAlignment = 'left' | 'center' | 'right';
4
+ export interface AdConfig {
5
+ slotId: string;
6
+ width: number;
7
+ height: number;
8
+ layout: AdLayout;
9
+ alignment?: AdAlignment;
10
+ }
11
+ export interface AdContainerProps {
12
+ adType: AdType;
13
+ className?: string;
14
+ }
15
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/ads/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,YAAY,CAAC;AAEhD,MAAM,MAAM,MAAM,GAEd,aAAa,GACb,aAAa,GACb,cAAc,GACd,cAAc,GAEd,aAAa,GAEb,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GAEX,gBAAgB,GAChB,iBAAiB,GAEjB,QAAQ,GAER,gBAAgB,GAChB,WAAW,GAEX,eAAe,GACf,eAAe,GACf,kBAAkB,CAAC;AAEvB,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEtD,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,QAAQ,CAAC;IACjB,SAAS,CAAC,EAAE,WAAW,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
@@ -0,0 +1 @@
1
+ export {};